
/*=====================================================================

  File:      FileBlockProcessor.cs
  Summary:   SQL Remote Blob Storage sample provider. Support file for 
             a provider that uses NTFS files as the blob store.
             The FileBlockProcessor class is used to assist with processing large sets of files.
             It returns files in blocks of a specified size, keeping track of where it left
             off when processing a directory structure. This allows processing, say, a collection
             of one million files in blocks of 10,000 without having to enumerate all the files
             at once, which could be prohibitively expensive.

  Date:	     June 24th, 2008

---------------------------------------------------------------------

  This file is part of the Microsoft SQL Server Code Samples.
  Copyright (C) Microsoft Corporation.  All rights reserved.

This source code is intended only as a supplement to Microsoft
Development Tools and/or on-line documentation.  See these other
materials for detailed information regarding Microsoft code samples.

THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY
KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
PARTICULAR PURPOSE.

======================================================= */


using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Runtime.ConstrainedExecution;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using Microsoft.Win32.SafeHandles;

using Microsoft.Data.BlobStores;

using FILETIME = System.Runtime.InteropServices.ComTypes.FILETIME;

namespace Microsoft.Data.BlobStores.FileBlobStore
{

    /// <summary>
    /// The FileBlockProcessor class is used to assist with processing large sets of files.
    /// It returns files in blocks of a specified size, keeping track of where it left
    /// off when processing a directory structure. This allows processing, say, a collection
    /// of one million files in blocks of 10,000 without having to enumerate all the files
    /// at once, which could be prohibitively expensive.
    /// </summary>
    internal 
    sealed
    class FileBlockProcessor : IDisposable
    {

        //--------------------------------------------------------------------+
        //                                                                    |
        #region Constructors

        /// <summary>
        /// Base constructor. Specify a starting path.
        /// </summary>
        internal
        FileBlockProcessor(string StartingPath)
            : this(StartingPath, "*", SearchOption.TopDirectoryOnly)
        {
        }

        /// <summary>
        /// Base constructor. Specify a starting path, search pattern and 
        /// subdirectory search option.
        /// </summary>
        internal
        FileBlockProcessor(string StartingPath, string SearchPattern, SearchOption Options)
        {
            Init(StartingPath, SearchPattern, Options);
        }

        /// <summary>
        /// IDisposable interface.
        /// Should not be overridden, base classes should override 
        /// the protected Dispose(bool) method.
        /// </summary>
        public
        void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        /// <summary>
        /// Finalizer.
        /// Should not be overridden, base classes should override the protected Dispose(bool) 
        /// method. The auto-generated subclass finalizer automatically chains to this base 
        /// finalizer, which will in turn call the correct virtual dispose method.
        /// </summary>
        ~FileBlockProcessor()
        {
            Dispose(false);
        }

        /// <summary>
        /// Clean up any handles or state.
        /// </summary>
        internal
        void Dispose(bool disposing)
        {
            if (!m_Disposed)
            {
                if (disposing)
                {
                    // Free managed resources
                    //
                    if (m_CurrentFindHandle != null)
                    {
                        m_CurrentFindHandle.Dispose();
                    }
                }

                // Free native resources.
                // This will be run in the finalizer case as well as explicit dispose.
                //
                m_Disposed = true;
            }
        }

        #endregion

        //--------------------------------------------------------------------+
        //                                                                    |
        #region Public Methods
        
        /// <summary>
        /// Retrieve the next block of file information objects.
        /// </summary>
        /// <param name="BlockSize">
        /// Number of files to return in this block.
        /// </param>
        /// <returns>
        /// Array of FileInfo objects.
        /// </returns>
        internal
        FileInfo[] GetNextFileBlock(int BlockSize)
        {
            List<FileInfo> results = new List<FileInfo>();

            DoGetNextFileBlock(BlockSize, results);

            while (results.Count < BlockSize && m_CurrentSubFolders != null && m_CurrentSubFolders.Count > 0)
            {
                if (m_CurrentFindHandle != null)
                {
                    m_CurrentFindHandle.Close();
                    m_CurrentFindHandle.SetHandleAsInvalid();
                }
                m_CurrentPath = m_CurrentSubFolders.Pop();
                DoGetNextFileBlock(BlockSize, results);
            }

            return results.ToArray();
        }

        /// <summary>
        /// Stop the enumeration. 
        /// Closes the file handles.
        /// </summary>
        internal
        void Close()
        {
            m_CurrentPath = null;
            m_CurrentSubFolders = null;

            // Close the Find handle.
            //
            this.Dispose();
        }

        #endregion

        //--------------------------------------------------------------------+
        //                                                                    |
        #region Internal Methods

        private
        void Init(string StartingPath, string SearchPattern, SearchOption Options)
        {
            m_StartingPath = StartingPath;
            m_SearchPattern = SearchPattern;
            m_Options = Options;
            m_CurrentPath = null;
            m_CurrentSubFolders = null;
        }

        private 
        void DoGetNextFileBlock(int BlockSize, List<FileInfo> results)
        {
            NativeMethods.WIN32_FIND_DATA fd = new NativeMethods.WIN32_FIND_DATA();
            int NumFound = results.Count;
            bool FirstFile = false;

            if (String.IsNullOrEmpty(m_CurrentPath))
            {
                m_CurrentPath = m_StartingPath;
            }

            if (m_CurrentFindHandle == null || m_CurrentFindHandle.IsInvalid)
            {
                string searchpath = m_CurrentPath.TrimEnd('\\') + "\\" + m_SearchPattern;
                m_CurrentFindHandle = NativeMethods.FindFirstFileW(searchpath, out fd);
                FirstFile = true;

                // Populate the list of subfolders for this path
                if (m_Options == SearchOption.AllDirectories)
                {
                    DirectoryInfo thisfolder = new DirectoryInfo(m_CurrentPath);
                    if (m_CurrentSubFolders == null)
                    {
                        m_CurrentSubFolders = new Stack<string>();
                    }

                    DirectoryInfo[] SubFolders = thisfolder.GetDirectories();
                    foreach (DirectoryInfo di in SubFolders)
                    {
                        m_CurrentSubFolders.Push(di.FullName);
                    }
                }
            }

            if (m_CurrentFindHandle == null || m_CurrentFindHandle.IsInvalid)
            {
                return;
            }

            bool FoundFile = true;

            while (FoundFile && NumFound < BlockSize)
            {
                if (!FirstFile)
                {
                    FoundFile = NativeMethods.FindNextFileW(m_CurrentFindHandle, out fd);
                    if (!FoundFile) 
                    {
                        int err = Marshal.GetLastWin32Error();
                        if (err != NativeMethods.ERROR_NO_MORE_FILES)
                        {
                            throw new Win32Exception(err);
                        }

                        break; 
                    }
                }
                else
                {
                    FirstFile = false;
                }

                if ((fd.dwFileAttributes & FileAttributes.Directory) == 0)
                {
                    if (fd.cFileName != "." && fd.cFileName != "..")
                    {
                        string filepath = Path.Combine(m_CurrentPath, fd.cFileName);
                        FileInfo fi = new FileInfo(filepath);
                        results.Add(fi);
                        NumFound++;
                    }
                }
            }
        }

        #endregion

        //--------------------------------------------------------------------+
        //                                                                    |
        #region Fields

        // Private member data
        //
        private string m_StartingPath;
        private string m_SearchPattern;
        private SearchOption m_Options;
        private string m_CurrentPath;
        private Stack<string> m_CurrentSubFolders;

        private FindFileSafeHandle m_CurrentFindHandle;
        private bool m_Disposed;

        #endregion


        //--------------------------------------------------------------------+
        //                                                                    |
        #region Interop Declarations

        /// <summary>
        /// Wraps the handle returned from FindFirstFile.
        /// </summary>
        internal
        sealed
        class FindFileSafeHandle : SafeHandleZeroOrMinusOneIsInvalid
        {
            [SecurityPermission(SecurityAction.LinkDemand, UnmanagedCode=true)]
            private 
            FindFileSafeHandle() : base(true) { }

            protected 
            override 
            bool ReleaseHandle()
            {
                return NativeMethods.FindClose(this.handle);
            }
        }

        /// <summary>
        /// Wrapper for DllImport calls.
        /// </summary>
        private
        class NativeMethods
        {
            private NativeMethods() { }

            // Interop declarations.
            //
            internal const int INVALID_HANDLE = -1;
            internal const int FILE_NOT_FOUND = 2;
            internal const int ERROR_NO_MORE_FILES = 18;

            [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
            internal struct WIN32_FIND_DATA
            {
                public FileAttributes dwFileAttributes;
                public FILETIME ftCreationTime;
                public FILETIME ftLastAccessTime;
                public FILETIME ftLastWriteTime;
                public uint nFileSizeHigh;
                public uint nFileSizeLow;
                public uint dwReserved0;
                public uint dwReserved1;
                [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)]
                public string cFileName;
                [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)]
                public string cAlternateFileName;
            }

            /// <summary>
            /// FindFirstFile from kernel32.dll.
            /// </summary>
            [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            internal static extern 
            FindFileSafeHandle FindFirstFileW(
                    string lpFileName, out WIN32_FIND_DATA lpFindFileData);

            /// <summary>
            /// FindNextFile from kernel32.dll.
            /// </summary>
            [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern
            bool FindNextFileW(FindFileSafeHandle hFindFile, out WIN32_FIND_DATA lpFindFileData);

            /// <summary>
            /// FindClose from kernel32.dll.
            /// </summary>
            [DllImport("kernel32.dll")]
            [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
            [return: MarshalAs(UnmanagedType.Bool)]
            internal static extern 
            bool FindClose(IntPtr hFindFile);
        }

        #endregion
    }
}
